package com.example.emos.wx.service.impl;

import cn.hutool.core.date.DateField;
import cn.hutool.core.date.DateRange;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpUtil;
import com.example.emos.wx.config.SystemConstants_系统常量数据;
import com.example.emos.wx.db.dao.*;
import com.example.emos.wx.db.pojo.TbCheckin;
import com.example.emos.wx.db.pojo.TbFaceModel;
import com.example.emos.wx.exception.EmosException;
import com.example.emos.wx.service.CheckinService;
import com.example.emos.wx.task.EmailTask;
import lombok.extern.slf4j.Slf4j;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Scope;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;

/**
 * @ClassName : CheckinServiceImpl
 * @Author : 骆发茂
 * @Date: 2021/10/20 21:00
 * @Description :
 */

@Service
@Slf4j
@Scope("prototype")//以后异步进程，多线程，发送邮件
public class CheckinServiceImpl implements CheckinService {

    @Autowired
    private TbCheckinDao checkinDao;

    @Autowired
    private TbHolidaysDao holidaysDao;

    @Autowired
    private TbWorkdayDao workdayDao;

    @Autowired
    private SystemConstants_系统常量数据 constants;

    @Autowired
    private TbUserDao userDao;

    @Value("${emos.email.system}")
    private String systemEmail;

    @Autowired
    private EmailTask emailTask;

    @Autowired
    private TbFaceModelDao faceModelDao;

    @Autowired
    private TbCityDao cityDao;

    @Value("${emos.face.createFaceModelUrl}")
    private String createFaceModelUrl;

    @Value("${emos.face.checkinUrl}")
    private String checkinUrl;

    @Value("${emos.email.hr}")
    private String hrEmail;

    @Value("${emos.code}")
    private String code;

    /**
     * 6-9 封装检测当天是否可以签到（业务层）-
     * 先判断当天能不能签到（是不是工作日（特殊的节假日？））,
     * holidaysDao，workdayDao，以及正常判断日常，三方面判断当天能不能签到
     *
     *
     * 如果是工作日，再判断当前可不可以签到（太早，太迟，或者已经签到过了？）
     * SystemConstants_系统常量数据，start，end判断时间点
     */
    @Override
    public String validCanCheckin(int userId, String date) {
        //不为空true，bool_1为true说明是假期
        boolean bool_1 = holidaysDao.searchTodayIsHoliday() != null ? true : false;
        //不为空true，bool_2为true说明是工作日
        boolean bool_2 = workdayDao.searchTodayIsWorkday() != null ? true : false;
        String type = "工作日";
        //判断当前是不是是周末，if（是，是周末）
        /**为什么用DateUtil.date()？这其实是hutool依赖库里面写的，
         * 比Java本身的Date是扩展的子类更多方法更好用*/
        if (DateUtil.date().isWeekend()) {
            type = "节假日";

        }

        //不单单上面
        // if (DateUtil.date().isWeekend())
        // 还要依靠下面来确定是不是特殊的工作日，节假日，另外的if elseif
        if (bool_1) {
            type = "节假日";
        } else if (bool_2) {
            type = "工作日";
        }

        if (type.equals("节假日")) {
            return "节假日不需要考勤";
        } else {
            DateTime now = DateUtil.date();
            String start = DateUtil.today() + " " + constants.attendanceStartTime;
            String end = DateUtil.today() + " " + constants.attendanceEndTime;

            //start，end转化为DateTime，DateTime的比较
            DateTime attendanceStart = DateUtil.parse(start);// 将日期字符串转化为日期。parse：解析
            DateTime attendanceEnd = DateUtil.parse(end);

            if (now.isBefore(attendanceStart)) {
                return "没到上班考勤开始时间";
            } else if (now.isAfter(attendanceEnd)) {
                return "超过了上班考勤结束时间";

            }
            //没有太早，太迟，就是在考勤时间段之内，再判断之前有没有签到过了
            else {
                HashMap map = new HashMap();
                map.put("userId", userId);
                map.put("date", date);
                map.put("start", start);
                map.put("end", end);
                //是否是不是空的，不是空的，说明签到过了
                //bool，true，签到过了
                boolean bool = checkinDao.haveCheckin(map) != null ? true : false;
                return bool ? "今日已经考勤，不用重复考勤" :"可以考勤";

            }

        }

    }


    //签到确定需要关注三部分->时间\人脸\地区风险->->->
    @Override
    public void checkin(HashMap param) {
        /**———————————————————————————————————————————时间———————————————————————————————————————————————————————————*/

        /*签到的日期*/
        Date d1 = DateUtil.date();
        /*签到打卡前*/
        Date d2 = DateUtil.parse(DateUtil.today() + " " + constants.attendanceEndTime);
        /*签到打卡结束之后，就是矿工*/
        Date d3 = DateUtil.parse(DateUtil.today() + " " + constants.attendanceStartTime);
        //打卡开始之前，1表示打卡成功，2表示迟到了，之外就是矿工，超过attendanceEndTime
        int status = 1;
        //开始之前
        if (d1.compareTo(d2) < 0) {
            status = 1;
        }         //开始之后，结束之前
        else if (d1.compareTo(d2) > 0 && d1.compareTo(d3) < 0) {
            status = 2;
        } else {
            throw new EmosException("超出考勤时间段，无法考勤，打工人矿工不干了");
        }


        /**—————————————————————————————————————————————人脸—————————————————————————————————————————————————————————*/
        //根据hash中取出数据userid
        int userId = (Integer) param.get("userId");
        //之前写的sql语句，根据userid查人脸模型
        String faceModel = faceModelDao.searchFaceModel(userId);
        if (faceModel == null) {
            throw new EmosException("不存在人脸模型，数据库中没有啊");
        }         //如果存在人脸模型，通过在centos中的python人脸模型对比，多种情况对比结果
        else {
            //人脸模型-》txt，放在path中
            String path = (String) param.get("path");
            //向python，centos发送http请求
            HttpRequest request = /**hutool里面的HttpUtil*/HttpUtil.createPost(checkinUrl);
            //前半部分拍照的人脸模型，后半部分faceModel数据库中的人脸模型，（名字叫、参数、人脸模型数据、名字叫
            request.form("photo", FileUtil.file(path), "targetModel", faceModel);
            //调用python程序的激活码
            request.form("code", code);
            HttpResponse response = request.execute();
            if (response.getStatus() != 200) {
                throw new EmosException("人脸识别服务异常");
            }
            String body = response.body();
            if ("无法识别出人脸".equals(body) || "照片中存在多张人脸".equals(body)) {
                throw new EmosException(body);
            } else if ("False".equals(body)) {
                throw new EmosException(body + "，签到无效，非本人签到");
            }
/**—————————————————————————————————————————————地理位置信息—————————————————————————————————————————————————————————*/

            //对比成功，还要获取hash中的其他数据，为之后存入保存签到记录TbCheckin实体保存数据
            else if ("True".equals(body)) {
                //查询疫情风险等级
                //1是低风险，2中风险，3高风险
                int risk = 1;
                String city = (String) param.get("city");
                String district = (String) param.get("district");
                String address = (String) param.get("address");
                String country = (String) param.get("country");
                String province = (String) param.get("province");
                //还要将地区的code传入html中，获取html里面的信息，作判断是不是高风险地区
                if (!StrUtil.isBlank(city) && StrUtil.isBlank(district)) {
                    String code = cityDao.searchCode(city);
                    //jsoup获取get->elements->elements中读取第一个->elements第一个里面最后一个p标签内容->对p标签内容判断
                    try {
                        String url = "http://m." + code + ".bendibao.com/news/yqdengji/?qu=" + district;
                        Document document = Jsoup.connect(url).get();
                        /**找到之后是放到集合里面，不管有多少个，就是放到集合里面，其实网页中就一个class是list-content
                         * 所哟要Element element = elements.get(0);取出第一个（其实就是只有一个但照样要取出第一个）*/
                        Elements elements = document.getElementsByClass("list-content");
                        if (elements.size() > 0) {
                            //取出第一个元素
                            Element element = elements.get(0);
                            String result = element.select("p:last-child").text();
                            //高风险，需要发送邮件
                            // result="高风险";
                            //如果是高风险地区，就发送邮件
                            if ("高风险".equals(result)) {
                                risk = 3;
                                //发送告警邮件
                                HashMap<String, String> map = userDao.searchNameAndDept(userId);
                                //tb_dept表中的dept_name
                                String name = map.get("name");
                                String deptName = map.get("dept_name");
                                deptName = deptName != null ? deptName : "";
                                SimpleMailMessage message = new SimpleMailMessage();
                                message.setTo(hrEmail);
                                message.setSubject("员工" + name + "身处高风险疫情地区警告");
                                message.setText(deptName + "员工" + name + "，" + DateUtil.format(new Date(), "yyyy年MM月dd日") + "处于" + address + "，属于新冠疫情高风险地区，请及时与该员工联系，核实情况！");
                                emailTask.sendAsync(message);
                            } else if ("中风险".equals(result)) {
                                risk = 2;
                            }

                        }

                    } catch (Exception e) {
                        e.printStackTrace();
                        throw new EmosException("获取风险等级失败");
                    }
                }

                //这里只要对比到人脸模型就存入数据
                // 以后可能不需要用到那部分了，但数据还是要存入的
                /**保存签到记录*/
                TbCheckin entity = new TbCheckin();
                entity.setUserId(userId);
                entity.setAddress(address);
                entity.setCountry(country);
                entity.setProvince(province);
                entity.setCity(city);
                entity.setDistrict(district);
                entity.setStatus((byte) status);
                entity.setRisk(risk);
                entity.setDate(DateUtil.today());
                entity.setCreateTime(d1);
            }

        }
//图片的左右剧中方法：有width，block模型，left，right-auto
    }

    //path是本地中的照片（照片地址的绝对路径），根据userid和照片创建model
    //创建人脸模型，向python中传照片
    //返回的body，以及userid，都传到tb_dept数据表中
    @Override
    public void createFaceModel(int userId, String path) {
        /**createFaceModelUrl地址*/
        HttpRequest request = HttpUtil.createPost(createFaceModelUrl);
        /**设置要上传的文件
         * name:photo  是与python约定的，python从name为photo里面取数据
         * 从里面path找到文件*/
        request.form("photo", FileUtil.file(path));
        //python验证码
        request.form("code", code);
        HttpResponse response = request.execute();
        String body = response.body();
        if ("无法识别出人脸".equals(body) || "照片中存在多张人脸".equals(body)) {
            throw new EmosException(body);
        }
        /**如果创建成功人脸，就塞入数据库中*/
        else {
            TbFaceModel entity = new TbFaceModel();
            entity.setUserId(userId);
            //在python中创建的人脸模型，不是在本地的
            entity.setFaceModel(body);
            faceModelDao.insertFaceModel(entity);
        }

    }

    /*    检查当天的签到情况,通过传入的userid，返回签到的信息*/
    @Override
    public HashMap searchTodayCheckin(int userId) {
        HashMap map = checkinDao.searchTodayCheckin(userId);
        return map;
    }

    /*返回查询结果所有的行数，查询userid的所有签到日期*/
    /** 根据userid的所有签到日期*/
    @Override
    public long searchCheckinDays(int userId) {
        long checkinDays = checkinDao.searchCheckinDays(userId);
        return checkinDays;
    }

    /** 8-4 实现考勤成功页面（业务层）*/
    /*根据传来的开始startime、结束日期endtime以及userid，查询开始到结束每天的签到情况->>>>>>返回日期date和状态status（正常、迟到）*/
   /*也就是说param应该是有开始startime、结束日期endtime以及userid的*/
    //用本周每天日期查询当天的考勤情况
    //    （1）先判断当天是工作日还是节假日（通过   正常情况、假日表、工作表判断）
    //    （2）未来的日子考勤结果为空字符串
    //    （3）当天考勤结束前，没考勤的结果为空字符串
    //    （4）工作日没有考勤记录就算旷工
    @Override
    public ArrayList<HashMap> searchWeekCheckin(HashMap param) {
/*——————————————————————————————————————————————确定参数——————————————————————————————————————————————————*/
        //checkinDao.searchWeekCheckin返回     日期和签到状态（正常、迟到）    用hashmap接收
        //其实list[0],list[1],list[2],list[3]等等每一个都是hashmap，每一个hashmap的key为日期，value为签到状态
        ArrayList<HashMap> checkinList = checkinDao.searchWeekCheckin(param);
        //    <!--param里面传入开始、结束时间，hashmap，返回假期日list-->
        ArrayList holidaysList = holidaysDao.searchHolidaysInRange(param);
        //    <!--param里面传入开始、结束时间，hashmap，返回工作日list-->
        ArrayList workdaysList = workdayDao.searchWorkdayInRange(param);
        //DateUtil.parse方法会自动识别一些常用格式，将string的内容日期转成date格式。parse:解析
        DateTime startDate = DateUtil.parseDate(param.get("startDate").toString());
        DateTime endDate = DateUtil.parseDate(param.get("endDate").toString());
        //DateUtil.range；创建日期范围生成器          生成本周七天的日期对象。（5+star+end=7天）
        //    DAY_OF_MONTH(5),->unit 步进单位
        DateRange range = DateUtil.range(startDate, endDate, DateField.DAY_OF_MONTH);
        ArrayList<HashMap> list = new ArrayList<>();
        //one，从开始到结束的日期的每一天，for循环
        range.forEach(one -> {
            String date = one.toString("yyyy-MM-dd");
            String type = "工作日";
/*——————————————————————————————————————————————确定参数——————————————————————————————————————————————————*/



/*------------------------------------判断抽取出来的是不是工作日-------------------------------------------*/
            if(one.isWeekend()){
                type="节假日";
            }
            if(holidaysList!=null&&holidaysList.contains(date)){
                type="节假日";
            }
            else if (workdaysList!=null&&workdaysList.contains(date)){
                type = "工作日";
            }
/*------------------------------------判断抽取出来的是不是工作日-------------------------------------------*/




/*------------------------------------判断过去的日子签到有没有缺勤-------------------------------------------*/
            //未来的日子考勤结果为空字符串
            String status = " ";
            /**range抽取出来的是工作日，而且日期与今天日期比较，是在今天之前发生的的情况下（就是过去的意思）
             * 一句话：过去的日子是工作日，默认先缺勤，如果从数据库中抽取到有之前签到的status，就status = map.get("status")*/
            if(type.equals("工作日")&&DateUtil.compare(one,DateUtil.date())<=0){
                //工作日没有考勤记录就算旷工,下面for在查找了，找不到就默认缺勤
                status="缺勤";
                boolean flag = false;
                /**ArrayList<HashMap> checkinList = checkinDao.searchWeekCheckin(param);
                 其实list[0],list[1],list[2],list[3]等等每一个都是hashmap，每一个hashmap的key为日期，value为签到状态
                 根据传来的开始、结束日期以及userid，查询开始到结束每天的签到情况，返回日期和状态-->checkinList的每一个是map*/
                for(HashMap<String, String> map:checkinList){
                    //意思是key是status。value是date，这里有问题？？？？？？？？？？？
                    //只想获取 key，可以使用 keySet() 方法
                    //只想获取 value，可以使用 values()
                    /**如果数据库中有之前签到的status，就记录下，并且退出for，flag=true是为了说明已经找到了
                     * 一句话：只要找到那一天，就退出for循环*/
                    if(map.containsValue(date)){
                        //map.get  获取指定 key 对应对 value
                        status = map.get("status");//status是签到状态（正常、迟到），获取的就是要这个，
                        flag = true;
                        break;
                    }
                }
/*------------------------------------判断过去的日子签到有没有缺勤-------------------------------------------*/




/*-------------------------------------当天考勤结束前，如果没考勤的结果为空字符串---------------------------------------*/
                DateTime endtime = DateUtil.parse(DateUtil.today() + " " + constants.attendanceEndTime);
                String today = DateUtil.today();
                /**(date.equals(today)意思是今天是今天；并且考勤结束时间还在当天签到时间后面，还没结束，flag是说明还没有签到，就status=“”*/
                if(date.equals(today)&&DateUtil.date().isBefore(endtime)&&flag==false){
                    status = "";
                }
/*-------------------------------------当天考勤结束前，没考勤的结果为空字符串---------------------------------------*/
            }

/*-----------------------------根据上面的for  each确定好一周内每天的情况，塞入ArrayList<HashMap>------------------------------------------------------*/
            HashMap map = new HashMap();
            map.put("date", date);
            map.put("status",status);
            map.put("type",type);//工作日？节假日
            map.put("day", one.dayOfWeekEnum().toChinese("周"));
            //        ArrayList<HashMap> list = new ArrayList<>();
            list.add(map);
        });
        return list;
/*-----------------------------根据上面的for  each确定好一周内每天的情况，塞入ArrayList<HashMap>------------------------------------------------------*/

    }

    /** 8-13实现我的考勤页面（业务层&持久层）(20-54)*/
    /*根据传来的开始、结束日期以及userid，查询开始到结束一个月的签到情况，返回日期和状态*/
    /**查询开始到结束每天的签到情况，返回日期和状态*/
    @Override
    public ArrayList<HashMap> searchMonthCheckin(HashMap param) {
        return this.searchWeekCheckin(param);
    }
}